#include "sysconfig.h"
#include "cia.h"
#include "custom.h"
#include "win32.h"

// #define IOPORT_EMU

#if IO_DEBUG_DEF
    #define io_log Logger::Write
#else
    #define io_log
#endif

#if PARA_DEBUG_DEF
    #define para_log Logger::Write
#else
    #define para_log
#endif

//  TVicPORT
typedef BOOL (_stdcall * OPENTVICPORT)();
static OPENTVICPORT pOpenTVicPort;
typedef void (_stdcall * CLOSETVICPORT)();
static CLOSETVICPORT pCloseTVicPort;
typedef BOOL (_stdcall * ISDRIVEROPENED)();
static ISDRIVEROPENED pIsDriverOpened;
typedef UCHAR (_stdcall * READPORT)(USHORT);
static READPORT pReadPort;
typedef UCHAR (_stdcall * WRITEPORT)(USHORT, UCHAR);
static WRITEPORT pWritePort;

//  WINIO
typedef bool (_stdcall * INITIALIZEWINIO)();
static INITIALIZEWINIO pInitializeWinIo;
typedef void (_stdcall * SHUTDOWNWINIO)();
static SHUTDOWNWINIO pShutdownWinIo;
typedef bool (_stdcall * GETPORTVAL)(WORD, PDWORD, BYTE);
static GETPORTVAL pGetPortVal;
typedef bool (_stdcall * SETPORTVAL)(WORD, DWORD, BYTE);
static SETPORTVAL pSetPortVal;

static HMODULE ioh;

static int initialized;

int ioport_init()
{
    if (initialized)
        return initialized > 0 ? 1 : 0;

    #ifndef IOPORT_EMU
    ioh = WIN32_LoadLibrary(L"tvicport.dll");
    if (ioh)
    {
        for (;;)
        {
            pOpenTVicPort = (OPENTVICPORT)GetProcAddress(ioh, "OpenTVicPort");
            pCloseTVicPort = (CLOSETVICPORT)GetProcAddress(ioh, "CloseTVicPort");
            pIsDriverOpened = (ISDRIVEROPENED)GetProcAddress(ioh, "IsDriverOpened");
            pReadPort = (READPORT)GetProcAddress(ioh, "ReadPort");
            pWritePort = (WRITEPORT)GetProcAddress(ioh, "WritePort");
            if (!pOpenTVicPort || !pCloseTVicPort || !pIsDriverOpened || !pReadPort || !pWritePort)
            {
                Logger::Write(L"IO: incompatible tvicport.dll\n");
                break;
            }
            if (!pOpenTVicPort())
            {
                Logger::Write(L"IO: tvicport.dll failed to initialize\n");
                break;
            }
            if (!pIsDriverOpened())
            {
                Logger::Write(L"IO: tvicport.dll failed to initialize!\n");
                pCloseTVicPort();
                break;
            }
            initialized = 1;
            Logger::Write(L"IO: tvicport.dll initialized\n");
            return 1;
        }
    }
    FreeLibrary(ioh);
    ioh = WIN32_LoadLibrary(L"winio.dll");
    if (ioh)
    {
        for (;;)
        {
            pInitializeWinIo = (INITIALIZEWINIO)GetProcAddress(ioh, "InitializeWinIo");
            pShutdownWinIo = (SHUTDOWNWINIO)GetProcAddress(ioh, "ShutdownWinIo");
            pGetPortVal = (GETPORTVAL)GetProcAddress(ioh, "GetPortVal");
            pSetPortVal = (SETPORTVAL)GetProcAddress(ioh, "SetPortVal");
            if (!pInitializeWinIo || !pShutdownWinIo || !pGetPortVal || !pSetPortVal)
            {
                Logger::Write(L"IO: incompatible winio.dll\n");
                break;
            }
            __try {
                initialized = pInitializeWinIo() ? 2 : 0;
            } __except(EXCEPTION_EXECUTE_HANDLER)
            {
                Logger::Write(L"IO: winio.dll initialization failed\n");
            }
            if (!initialized)
                break;
            Logger::Write(L"IO: winio.dll initialized\n");
            return 1;
        }
    }
    FreeLibrary(ioh);
    initialized = -1;
    Logger::Write(L"IO: tvicport.dll or winio.dll failed to initialize\n");
    return 0;
    #else
    initialized = 1;
    return 1;
    #endif
}

void ioport_free()
{
    #ifndef IOPORT_EMU
    if (initialized == 1)
        pCloseTVicPort();
    if (initialized == 2)
        pShutdownWinIo();
    if (initialized)
        FreeLibrary(ioh);
    #endif
    initialized = 0;
}

byte ioport_read(int port)
{
    DWORD v = 0;
    #ifndef IOPORT_EMU
    if (initialized == 1)
        v = pReadPort(port);
    else if (initialized == 2)
        pGetPortVal(port, &v, 1);
    #endif
    io_log(L"ioport_read %04X returned %02X\n", port, v);
    return (byte)v;
}

void ioport_write(int port, byte v)
{
    #ifndef IOPORT_EMU
    if (initialized == 1)
        pWritePort(port, v);
    else if (initialized == 2)
        pSetPortVal(port, v, 1);
    #endif
    io_log(L"ioport_write %04X %02X\n", port, v);
}

#ifndef PARALLEL_DIRECT

void paraport_free()
{
}
int paraport_init()
{
    return 0;
}
int paraport_open(const TCHAR* port)
{
    return 0;
}
int parallel_direct_write_status(byte v, byte dir)
{
    return 0;
}
int parallel_direct_read_status(byte* vp)
{
    return 0;
}
int parallel_direct_write_data(byte v, byte dir)
{
    return 0;
}
int parallel_direct_read_data(byte* v)
{
    return 0;
}

#else

    #include "paraport/ParaPort.h"

typedef BOOL (*closePort)(HANDLE);
typedef BOOL (*executeCycle)(HANDLE, PARAPORT_CYCLE*, int);

// typedef BOOL (*getPortInfo)(HANDLE, PARAPORT_INFO*); tomanu - fix

typedef HANDLE* (*openPort)(const char*);
static closePort pp_closeport;
static executeCycle pp_executecycle;
// static getPortInfo pp_getportinfo; tomanu - fix
static openPort pp_openport;
static HMODULE para;
static HANDLE pport;

void paraport_free()
{
    if (para)
    {
        if (pport)
            pp_closeport(pport);
        pport = 0;
        FreeLibrary(para);
        para = 0;
    }
}

int paraport_init()
{
    int mask = 0, i;
    HANDLE pp;

    paraport_free();
    para = WIN32_LoadLibrary(L"ParaPort.dll");
    if (!para)
    {
        if (Logging::IO_DEBUG)
            Logger::Write(L"PARAPORT: no ParaPort.dll, direct parallel port emulation disabled\n");
        return 0;
    }
    pp_closeport = (closePort)GetProcAddress(para, "closePort");
    pp_executecycle = (executeCycle)GetProcAddress(para, "executeCycle");
    // pp_getportinfo = (getPortInfo)GetProcAddress (para, "getPortInfo"); tomanu - fix
    pp_openport = (openPort)GetProcAddress(para, "openPort");
    if (!pp_openport || !pp_closeport || !pp_executecycle)
    {
        Logger::Write(L"PARAPORT: GetProcAddress() failed\n");
        paraport_free();
    }
    Logger::Write(L"PARAPORT:");
    for (i = 0; i < 4; i++)
    {
        char tmp[10];
        sprintf(tmp, "LPT%d", i + 1);
        pp = pp_openport(tmp);
        if (pp != INVALID_HANDLE_VALUE)
        {
            mask |= 1 << i;
            pp_closeport(pp);
        }
        pp = 0;
    }
    if (!mask)
        Logger::Write(L"no parallel ports detected");
    Logger::Write(L"\n");
    return mask;
}

int paraport_open(const TCHAR* port)
{
    static TCHAR oldport[10];
    PARAPORT_CYCLE c[1];
    char* port2;

    if (!para)
        return 0;
    if (pport && !_tcscmp(port, oldport))
        return 1;
    port2 = Unicode::ua(port);
    pport = pp_openport(port2);
    free(port2);
    if (!pport)
    {
        Logger::Write(L"PARAPORT: couldn't open '%s'\n", port);
        paraport_free();
        return 0;
    }
    _tcscpy(oldport, port);
    Logger::Write(L"PARAPORT: port '%s' opened\n", port);
    memset(c, 0, sizeof(PARAPORT_CYCLE));
    c[0].MaskControl = PARAPORT_MASK_CONTROL | PARAPORT_MASK_CONTROL_DIRECTION;
    c[0].Control = PARAPORT_MASK_CONTROL_INIT | PARAPORT_MASK_CONTROL_DIRECTION;
    if (!pp_executecycle(pport, c, 1))
    {
        Logger::Write(L"PARAPORT: init executeCycle failed\n");
    }
    return 1;
}

int parallel_direct_write_status(byte v, byte dir)
{
    PARAPORT_CYCLE c[1];
    int ok = 1;

    if (!pport)
        return 0;
    memset(c, 0, sizeof(PARAPORT_CYCLE));
    c[0].MaskControl = PARAPORT_MASK_CONTROL_SELECTIN;
    if ((dir & 1))
    {
        Logger::Write(L"PARAPORT: BUSY can't be output\n");
        ok = 0;
    }
    if ((dir & 2))
    {
        Logger::Write(L"PARAPORT: POUT can't be output\n");
        ok = 0;
    }
    if ((dir & 4) && !(v & 4))
        c[0].Control |= PARAPORT_MASK_CONTROL_SELECTIN;
    if (!pp_executecycle(pport, c, 1))
    {
        Logger::Write(L"PARAPORT: write executeCycle failed, CTL=%02X DIR=%02X\n", v & 7, dir & 7);
        return 0;
    }
    para_log(L"PARAPORT: write CTL=%02X DIR=%02X\n", v & 7, dir & 7);
    return ok;
}

int parallel_direct_read_status(byte* vp)
{
    PARAPORT_CYCLE c[1];
    int ok = 1;
    byte v = 0;
    static int oldack;

    if (!pport)
        return 0;
    memset(c + 0, 0, sizeof(PARAPORT_CYCLE));
    c[0].MaskStatus = PARAPORT_MASK_STATUS;
    if (!pp_executecycle(pport, c, 1))
    {
        Logger::Write(L"PARAPORT: CTL read executeCycle failed\n");
        return 0;
    }
    if (c[0].Status & PARAPORT_MASK_STATUS_SELECT)
        v |= 4;
    if (c[0].Status & PARAPORT_MASK_STATUS_PAPEREND)
        v |= 2;
    if (!(c[0].Status & PARAPORT_MASK_STATUS_BUSY))
        v |= 1;
    if (c[0].Status & PARAPORT_MASK_STATUS_ACKNOWLEDGE)
    {
        v |= 8;
        if (!oldack)
            cia_parallelack();
        oldack = 1;
    }
    else
    {
        oldack = 0;
    }
    para_log(L"PARAPORT: read CTL=%02X\n", v);
    v &= 7;
    *vp &= ~7;
    *vp |= v;
    return ok;
}

int parallel_direct_write_data(byte v, byte dir)
{
    PARAPORT_CYCLE c[3];
    // int ok = 1;

    if (!pport)
        return 0;
    if (dir != 0xff)
    {
        Logger::Write(L"PARAPORT: unsupported mixed i/o attempted, DATA=%02X DIR=%02X, ignored\n", v, dir);
        return 0;
    }
    memset(c, 0, 3 * sizeof(PARAPORT_CYCLE));

    c[0].Data = v;
    c[0].MaskData = 0xff;
    c[0].MaskControl = PARAPORT_MASK_CONTROL_STROBE;

    c[1].MaskControl = PARAPORT_MASK_CONTROL_STROBE;
    c[1].Control = PARAPORT_MASK_CONTROL_STROBE;

    c[2].MaskControl = PARAPORT_MASK_CONTROL_STROBE;

    if (!pp_executecycle(pport, c, 3))
    {
        Logger::Write(L"PARAPORT: write executeCycle failed, data=%02X\n", v);
        return 0;
    }
    para_log(L"PARAPORT: write DATA=%02X\n", v);
    return 1;
}

int parallel_direct_read_data(byte* v)
{
    static byte olda, oldb;
    PARAPORT_CYCLE c[3];
    int ok = 1;

    if (!pport)
        return 0;
    memset(c, 0, 3 * sizeof(PARAPORT_CYCLE));

    c[0].MaskData = 0xff;
    c[0].MaskControl = PARAPORT_MASK_CONTROL_DIRECTION | PARAPORT_MASK_CONTROL_STROBE;
    c[0].Control = PARAPORT_MASK_CONTROL_DIRECTION;

    c[1].MaskControl = PARAPORT_MASK_CONTROL_STROBE;
    c[1].Control = PARAPORT_MASK_CONTROL_STROBE;

    c[2].MaskControl = PARAPORT_MASK_CONTROL_STROBE;

    if (!pp_executecycle(pport, c, 3))
    {
        Logger::Write(L"PARAPORT: DATA read executeCycle failed\n");
        return 0;
    }
    *v = c[0].Data;
    para_log(L"PARAPORT: read DATA=%02X\n", v);
    return ok;
}

#endif